/******************************************************************************
 *      Mestrado em Ciência da Computação - UFMS
 *		Redes de Computadores - Prof. Dr. Ronaldo Alves Ferreira
 *		Alunos: Geraldo Landre, Marcel Tolentino, Vinícius Bueno
 *****************************************************************************/
#include "port.h"
#include "tcb.h"

/**
 * Cria uma nova porta, com buffer para mensagens atribuido.
 * Insere nova porta no array de portas
 * @param bufflength
 * @return id da porta
 */
int newport_network(u_short p, u_short bufflength) {
    if(p > TCP_NPORTS || (p != 0 && p < TCP_NPORTS_KERNEL) )    // caso p seja muito grande, caso p seja negativo, caso p esteja na faixa do kernel
        return PERR_BUFFBOUND;

    sem_wait(&port_buff_mutex);

    if(p != 0 && port_buff[p]->state == PORT_OCCUPIED)          // caso a porta esteja ocupada
    {
        sem_post(&port_buff_mutex);
        return PERR_OCCUPIED;
    }
    

    u_short index = p;
    // caso seja passado zero, retorna uma porta aleatoria
    if(p == 0)
        index = sync_dequeue(free_ports_network);        // retira da fila de livres
    else
        sync_remove_element(free_ports_network,index);
    
    port_buff[index]->msg_buffer = sync_newqueue(bufflength);  // aloca um buffer de msgs
    port_buff[index]->buff_max_length = bufflength;
    port_buff[index]->state = PORT_OCCUPIED;                 // altera o estado para new

    sem_post(&port_buff_mutex);
    return index;
}
int newport_kernel(u_short p, u_short bufflength) {
    if(p < 0 || p > TCP_NPORTS_KERNEL)   // caso p seja negativo, caso p nao esteja na faixa do kernel
        return PERR_BUFFBOUND;

    sem_wait(&port_buff_mutex);

    if(p != 0 && port_buff[p]->state == PORT_OCCUPIED)          // caso a porta esteja ocupada
    {
        sem_post(&port_buff_mutex);
        return PERR_OCCUPIED;
    }


    u_short index = p;
    // caso seja passado zero, retorna uma porta aleatoria
    if(p == 0)
        index = sync_dequeue(free_ports_kernel);        // retira da fila de livres
    else
        sync_remove_element(free_ports_kernel,index);

    port_buff[index]->msg_buffer = sync_newqueue(bufflength);  // aloca um buffer de msgs
    port_buff[index]->buff_max_length = bufflength;
    port_buff[index]->state = PORT_OCCUPIED;                 // altera o estado para new

    sem_post(&port_buff_mutex);
    return index;
}
int newport(u_short p, u_short bufflength) {
    if(p > 0 && p <= TCP_NPORTS_KERNEL)
        return newport_kernel(p, bufflength);
    return newport_network(p, bufflength);
}
/**
 * Desaloca a port cujo id foi passado
 * @param id id da port
 */
void freeport(u_short i) {

    if(i > TCP_NPORTS )
        return;

    switch(port_buff[i]->state) {
        case PORT_FREE:
            break;
        case PORT_OCCUPIED:
            //printf("antes do freequeue\n");
            sync_freequeue(port_buff[i]->msg_buffer);  // limpa o buffer de msgs
            port_buff[i]->state = PORT_FREE;    // altera o estado pra free

            //printf("depois do freequeue\n");
            if(i <= TCP_NPORTS_KERNEL)
                sync_enqueue(free_ports_kernel, i);               // adiciona na fila de livres
            else 
                sync_enqueue(free_ports_network, i);               // adiciona na fila de livres

            break;
        default:
            break;
    }
}
/**
 * Altera o tamanho da porta
 * @param id id da port
 */
void resizeport(u_short i, u_short newlength) {
    if(i <= 0 || i > TCP_NPORTS )
        return;

    switch(port_buff[i]->state) {
        case PORT_FREE:
            break;
        case PORT_OCCUPIED:
            sync_freequeue(port_buff[i]->msg_buffer);  // limpa o buffer de msgs
            port_buff[i]->msg_buffer = sync_newqueue(newlength);
            break;
        default:
            break;
    }
}

/**
 * Envia uma mensagem para a port cujo id foi passado
 * @param id id da port para a qual se deseja enviar a msg
 * @param msg mensagem a ser enviada
 */
int port_sendmsg(u_short id_dest, int msg) {
    if(id_dest > TCP_NPORTS)
        return PERR_BUFFBOUND;
    
    switch(port_buff[id_dest]->state) {
        case PORT_OCCUPIED:
            break; // OK
        default:
            return PERR_NOTOCCUPIED;
    }
    
    _port_sendmsg(id_dest, msg);
    return 0;
}
// caso precise ser usado no processo principal
void _port_sendmsg(u_short id_dest, int msg) {
    sync_enqueue(port_buff[id_dest]->msg_buffer, msg);
}

/**
 * espera por uma mensagem
 * @return mensagem recebida
 */
int port_recvmsg(u_short i) {
    if(i > TCP_NPORTS)
        return PERR_BUFFBOUND;
    
    int msg;
    if(port_buff[i]->state != PORT_OCCUPIED)
        return PERR_NOTOCCUPIED;

    msg = sync_dequeue(port_buff[i]->msg_buffer);

    return msg;
}

/**
 * conta quantas mensagens existem na fila da port i
 * @param i id da port
 * @return num de msgs na fila da port i
 */
u_short port_countmsg(u_short i) {
    if(i > TCP_NPORTS)
        return PERR_BUFFBOUND;

    switch(port_buff[i]->state) {
        case PORT_OCCUPIED:
            return sync_queue_length(port_buff[i]->msg_buffer);
        default:
            return port_buff[i]->buff_max_length; // tamanho maximo
    }
}

u_short port_maxlength(u_short i) {
    if(i > TCP_NPORTS)
        return PERR_BUFFBOUND;
    return port_buff[i]->buff_max_length;
}

// -------------------------------------------
// buffer de ports:
// -------------------------------------------

void init_port_buff()
{
    
    port_buff = (port**) malloc( TCP_NPORTS * sizeof(port*) + 1);
    free_ports_kernel = sync_newqueue(TCP_NPORTS_KERNEL + 1); // +1 = cartola (nada nessa mao...)
    free_ports_network = sync_newqueue(TCP_NPORTS - TCP_NPORTS_KERNEL + 1); // +1 = cartola (nada nessa mao...)

    sem_init(&port_buff_mutex , 0, 1);
    
    int i;
    // portas reservadas
    for (i = 1; i <= TCP_NPORTS_KERNEL; ++i) {
        port_buff[i] = (port*) malloc(sizeof(port));
        port_buff[i]->msg_buffer = NULL;
        port_buff[i]->state = PORT_FREE;
        sync_enqueue(free_ports_kernel, i);
    }
    // portas disponiveis para a rede
    for (i = TCP_NPORTS_KERNEL + 1; i <= TCP_NPORTS; ++i) {
        port_buff[i] = (port*) malloc(sizeof(port));
        port_buff[i]->msg_buffer = NULL;
        port_buff[i]->state = PORT_FREE;
        sync_enqueue(free_ports_network, i);
    }
}

// TODO: testar esta funcao
void destroy_port_buff() {
    int i;
    for (i = TCP_NPORTS; i > 0; --i) {
        freeport(i);
        free(port_buff[i]);
    }
    sync_freequeue(free_ports_kernel);    
    sync_freequeue(free_ports_network);
    sem_destroy(&port_buff_mutex);
    free(port_buff);
}

//
// Testing
//
const char* str_port_state(port_state state) {
    switch(state) {
        case PORT_FREE:
            return "free";
        case PORT_OCCUPIED:
            return "occupied";
        default:
            return "uknown";
    }
}
const char* str_port_error(int error) {
    switch(error) {
        case PERR_NOTOCCUPIED:
            return "Destination port not occupied.";
        default:
            return "uknown";
    }
}
void print_port(int i) {
    printf("[%d] ", i);
    if(i < 1)
        return;
    if(port_buff != NULL)
        printf("state: %s\t - ", str_port_state(port_buff[i]->state));
    if(port_buff == NULL)
        printf("null buffer\n");
    else if(port_buff[i] == NULL)
        printf("null port\n");
    else
        print_sync_queue_status(port_buff[i]->msg_buffer);
}

void print_port_buff() {
    int i;
    printf("----- global buffer of ports -----\n");
    if(port_buff == NULL)
        printf("NULL\n");
    else
        for(i = 0; i < TCP_NPORTS; ++i)
            print_port(i);
    printf("------------------------------------\n");
}

